package com.huawang.mdesign.model.service.inter;

import com.huawang.business.command.CommandAddType;
import com.huawang.business.command.CommandParameter;
import com.huawang.business.constants.ErrorCode;
import com.huawang.business.constants.ModelActionName;
import com.huawang.business.constants.ModelConstantURI;
import com.huawang.business.dto.ModifyPropertyDTO;
import com.huawang.business.dto.project.Project;
import com.huawang.business.exception.AbortExecutionException;
import com.huawang.business.service.model.base.UmlService;
import com.huawang.business.service.model.core.InternalOwnerService;
import com.huawang.business.service.model.core.InternalUmlService;
import com.huawang.business.service.model.core.project.ProjectManager;
import com.huawang.business.service.model.core.property.PropertyManager;
import com.huawang.business.service.transaction.TransactionServiceImpl;
import com.huawang.business.util.InstanceVoUtils;
import com.huawang.business.vo.ModelResultVo;
import com.huawang.core.definition.mof.UMLDefinitionConstants;
import com.huawang.core.definition.mof.instances.MetaClassInstance;
import com.huawang.core.semantics.constants.TypeEnum;
import com.huawang.core.semantics.entity.queryResult.MofResultContainer;
import com.huawang.core.semantics.entity.queryResult.QueryPropertyForm;
import com.huawang.core.semantics.entity.transaction.LocalTransaction;
import com.huawang.core.semantics.exception.LocalTransactionNotStartException;
import com.huawang.core.semantics.exception.TransactionNotExistingException;
import com.huawang.mdesign.model.service.constant.LogInfoEnum;
import com.huawang.mdesign.model.service.constant.ProjectSpecificationEnum;
import com.huawang.mdesign.model.service.constant.UAFDefinitionConstants;
import com.huawang.mdesign.model.service.dto.ConnectionPointReferenceClassificationPointDTO;
import com.huawang.mdesign.model.service.dto.DragBehaviorToStateDTO;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.huawang.business.constants.ModelActionName.ADD_MODEL;
import static com.huawang.mdesign.model.service.util.LogUtil.printOnlineLogs;
import static com.huawang.mdesign.model.service.util.LogUtil.printSerializeOnlineLogs;

/**
 * @Description 状态机图的对应Service
 * @Date 2022/3/7
 * @Author baibf
 */
@Service
@Primary
@Slf4j
public class StateMachinesService {

    private final UmlService umlService;
    private final InternalUmlService internalUmlService;
    private final PropertyManager propertyManager;
    private final ProjectManager projectManager;

    @Autowired
    private InternalOwnerService internalOwnerService;

    @Autowired
    private TransactionServiceImpl transactionService;

    public StateMachinesService(
            final UmlService umlService,
            final InternalUmlService internalUmlService,
            final ProjectManager projectManager,
            final PropertyManager propertyManager) {

        this.umlService = umlService;
        this.internalUmlService = internalUmlService;
        this.projectManager = projectManager;
        this.propertyManager = propertyManager;
    }

    /**
     * @param instanceId 区域的模型Id
     * @return boolean 如果区域内有状态，返回true，否则返回false
     * @description 判断状态机图中区域中是否只有一个初始状态
     * @author baibf
     * @date 2022/3/11
     */
    public boolean isExistPseudostateInitial(final String projectId, final String instanceId) {
        printOnlineLogs("projectId: {}, instanceI: {}", projectId, instanceId);
        return internalOwnerService.isExistPseudostateInitial(projectId, instanceId);
    }

    /**
     * @param dragBehaviorToStateDto
     * @return com.huawang.mdesign.model.service.vo.ModelResultVo
     * @description 模型树上拖拽一个behavior到状态中，会选择设置状态的入口/出口/执行活动；
     * 状态下会创建活动，活动名称为拖拽模型的名称，活动下创建活动图，活动图中有用控制流连接的活动起点，调用行为动作，活动终点；
     * 此时状态的入口/出口/执行活动属性值为创建的活动，拖拽的模型为调用行为动作的行为属性值；
     * @author baibf
     * @date 2022/3/11
     */
    @NotNull
    public ModelResultVo dragBehaviorToState(DragBehaviorToStateDTO dragBehaviorToStateDto) {
        printSerializeOnlineLogs("dragBehaviorToStateDto: ", dragBehaviorToStateDto);
        final ModelResultVo modelResultVo = new ModelResultVo();
        final String projectId = dragBehaviorToStateDto.getProjectId();
        MetaClassInstance behaviorInstance = internalUmlService.fetchInstance(
                dragBehaviorToStateDto.getBehaviorInstanceId(), projectId);
        if (Objects.isNull(behaviorInstance) || !internalUmlService.checkInstanceType(
                projectId, behaviorInstance, UMLDefinitionConstants.Behavior)) {
            throw AbortExecutionException.build(ErrorCode.MODEL_CONSTRAINT_ERROR);
        }

        final LocalTransaction transaction = transactionService.createTransaction(projectId, ADD_MODEL);
        try {
            String behaviorName = internalUmlService.queryStringProperty(projectId,
                    behaviorInstance, UMLDefinitionConstants.NamedElement_name);
            ActivityStereotypeWithDiagram activityStereotypeWithDiagram = getActivityByState(projectId, dragBehaviorToStateDto.getStateInstanceId());
            // 在状态下创建活动
            CommandParameter activityParameter = CommandParameter.builder()
                    .projectId(projectId)
                    .typeId(UMLDefinitionConstants.Activity)
                    .type(CommandAddType.MODEL)
                    .parentId(null)
                    .instanceId(ModelConstantURI.initInstanceId())
                    .applyStereotypes(activityStereotypeWithDiagram.getActivityAppliedStereotypes())
                    .defaultName(behaviorName)
                    .build();
            MetaClassInstance activityInstance = umlService.addModel(activityParameter);
            // 创建活动图
            CommandParameter sysMLActivityDiagramParameter = CommandParameter.builder()
                    .projectId(projectId)
                    .typeId(UMLDefinitionConstants.UMLDiagram)
                    .applyStereotypes(Collections.singletonList(activityStereotypeWithDiagram.getActivityDiagram()))
                    .type(CommandAddType.MODEL)
                    .parentId(activityInstance.getId())
                    .instanceId(ModelConstantURI.initInstanceId())
                    .defaultName(behaviorName)
                    .build();
            umlService.addModel(sysMLActivityDiagramParameter);
            // 在活动图中创建活动起点，调用行为动作，活动终点，并用控制流连接，且将调用行为动作的的行为设置为拖拽的模型
            CommandParameter initialNodeParameter = CommandParameter.builder()
                    .projectId(projectId)
                    .typeId(UMLDefinitionConstants.InitialNode)
                    .type(CommandAddType.MODEL)
                    .parentId(activityInstance.getId())
                    .instanceId(ModelConstantURI.initInstanceId())
                    .build();
            MetaClassInstance initialNodeInstance = umlService.addModel(initialNodeParameter);

            CommandParameter callBehaviorActionParameter = CommandParameter.builder()
                    .projectId(projectId)
                    .typeId(UMLDefinitionConstants.CallBehaviorAction)
                    .type(CommandAddType.MODEL)
                    .parentId(activityInstance.getId())
                    .instanceId(ModelConstantURI.initInstanceId())
                    .applyStereotypes(activityStereotypeWithDiagram.getCallBehaviorActionAppliedStereotypes())
                    .build();
            MetaClassInstance callBehaviorActionInstance = umlService.addModel(callBehaviorActionParameter);
            final ModifyPropertyDTO modifyPropertyDto = new ModifyPropertyDTO();
            modifyPropertyDto.setProjectId(projectId);
            modifyPropertyDto.setIsMultiplicity(false);
            modifyPropertyDto.setInstanceId(callBehaviorActionInstance.getId());
            modifyPropertyDto.setKey(UMLDefinitionConstants.CallBehaviorAction_behavior);
            modifyPropertyDto.setDataType(TypeEnum.INSTANCE.getName());
            modifyPropertyDto.setValue(Stream.of(dragBehaviorToStateDto.getBehaviorInstanceId()).collect(Collectors.toList()));
            propertyManager.updatePropertyValue(modifyPropertyDto);

            CommandParameter activityFinalNodeParameter = CommandParameter.builder()
                    .projectId(projectId)
                    .typeId(UMLDefinitionConstants.ActivityFinalNode)
                    .type(CommandAddType.MODEL)
                    .parentId(activityInstance.getId())
                    .instanceId(ModelConstantURI.initInstanceId())
                    .build();
            MetaClassInstance activityFinalNodeInstance = umlService.addModel(activityFinalNodeParameter);

            CommandParameter firstControlFlowParameter = CommandParameter.builder()
                    .projectId(projectId)
                    .typeId(UMLDefinitionConstants.ControlFlow)
                    .type(CommandAddType.RELATIONSHIP)
                    .parentId(activityInstance.getId())
                    .instanceId(ModelConstantURI.initInstanceId())
                    .source(initialNodeInstance.getId())
                    .target(callBehaviorActionInstance.getId())
                    .build();
            umlService.addModel(firstControlFlowParameter);

            CommandParameter secondControlFlowParameter = CommandParameter.builder()
                    .projectId(projectId)
                    .typeId(UMLDefinitionConstants.ControlFlow)
                    .type(CommandAddType.RELATIONSHIP)
                    .parentId(activityInstance.getId())
                    .instanceId(ModelConstantURI.initInstanceId())
                    .source(callBehaviorActionInstance.getId())
                    .target(activityFinalNodeInstance.getId())
                    .build();
            umlService.addModel(secondControlFlowParameter);

            umlService.updateModel(projectId, dragBehaviorToStateDto.getStateInstanceId(),
                    dragBehaviorToStateDto.getPropertyKey(), activityInstance);
            transactionService.commit(projectId);
            InstanceVoUtils.fillModelResultVo(modelResultVo, transaction);
        } catch (TransactionNotExistingException | LocalTransactionNotStartException e) {
            throw AbortExecutionException.build(ErrorCode.TRANSACTION_ERROR, e);
        } catch (Exception e) {
            transactionService.rollBack(projectId, transaction);
            throw AbortExecutionException.build(ErrorCode.SERVICE_ERROR, e);
        }
        return modelResultVo;
    }

    /**
     * @param connectionPointReferenceClassificationPointDto
     * @return com.huawang.mdesign.model.service.vo.ModelResultVo
     * @description 子机状态上拖入一个结合连接器，需要根据入口点和出口点为结合连接器的出口/入口属性设置值
     * @author baibf
     * @date 2022/4/29
     */
    @NotNull
    public ModelResultVo connectionPointReferenceClassificationSetPoint(
            final ConnectionPointReferenceClassificationPointDTO connectionPointReferenceClassificationPointDto) {
        printSerializeOnlineLogs("connectionPointReferenceClassificationPointDto: ", connectionPointReferenceClassificationPointDto);
        final String projectId = connectionPointReferenceClassificationPointDto.getProjectId();
        final ModelResultVo modelResultVo = new ModelResultVo();
        LocalTransaction transaction = transactionService.createTransaction(projectId, ModelActionName.UPDATE_PROPERTY);
        List<String> pseudoStatePointIds = connectionPointReferenceClassificationPointDto.getPseudoStatePointIds();
        final List<MetaClassInstance> entryPointInstances = new ArrayList<>();
        final List<MetaClassInstance> exitPointInstances = new ArrayList<>();
        List<MetaClassInstance> metaClassInstanceList = internalUmlService.fetchInstanceByIds(pseudoStatePointIds, projectId);
        QueryPropertyForm queryPropertyForm = new QueryPropertyForm();
        queryPropertyForm.put(metaClassInstanceList, UMLDefinitionConstants.Pseudostate_kind);
        MofResultContainer container = internalUmlService.queryProperty(projectId, queryPropertyForm);
        metaClassInstanceList.forEach(pointInstance -> {
            MetaClassInstance pseudoStateKindInstance = container.queryInstanceProperty(pointInstance.getId(),
                    UMLDefinitionConstants.Pseudostate_kind);

            if (Objects.nonNull(pseudoStateKindInstance)) {
                if (Objects.equals(pseudoStateKindInstance.getId(),
                        UMLDefinitionConstants.PseudostateKind_entryPoint)) {
                    entryPointInstances.add(pointInstance);
                    return;
                }

                if (Objects.equals(pseudoStateKindInstance.getId(),
                        UMLDefinitionConstants.PseudostateKind_exitPoint)) {
                    exitPointInstances.add(pointInstance);
                }
            }
        });

        try {
            umlService.updateModel(projectId,
                    connectionPointReferenceClassificationPointDto.getConnectionPointReferenceId(),
                    UMLDefinitionConstants.ConnectionPointReference_entry, entryPointInstances);
            umlService.updateModel(projectId,
                    connectionPointReferenceClassificationPointDto.getConnectionPointReferenceId(),
                    UMLDefinitionConstants.ConnectionPointReference_exit, exitPointInstances);
            modelResultVo.setInstanceId(connectionPointReferenceClassificationPointDto.getConnectionPointReferenceId());
            transactionService.commit(projectId);
            InstanceVoUtils.fillModelResultVo(modelResultVo, transaction);
        } catch (TransactionNotExistingException | LocalTransactionNotStartException e) {
            throw AbortExecutionException.build(ErrorCode.TRANSACTION_ERROR, e);
        } catch (AbortExecutionException e) {
            transactionService.rollBack(projectId, transaction);
            throw e;
        } catch (Exception e) {
            transactionService.rollBack(projectId, transaction);
            throw AbortExecutionException.build(ErrorCode.PROPERTY_UPDATE_ERROR, e);
        }

        return modelResultVo;
    }

    /**
     * @param projectId
     * @param instanceId
     * @return boolean
     * @description 判断PseudoState的传入传出
     * @author baibf
     * @date 2022/4/29
     */
    public boolean isExistIncomingOrOutgoing(final String projectId, final String instanceId) {
        printOnlineLogs("projectId: {}, instanceId: {}", projectId, instanceId);
        MetaClassInstance instance = internalUmlService.fetchInstance(instanceId, projectId);
        if (Objects.isNull(instance)) {
            return false;
        }
        MetaClassInstance pseudoStateKindInstance = internalUmlService.queryInstanceProperty(projectId, instance,
                UMLDefinitionConstants.Pseudostate_kind);
        if (Objects.nonNull(pseudoStateKindInstance)) {
            // 分支节点只能有一个传入
            if (UMLDefinitionConstants.PseudostateKind_fork.equals(pseudoStateKindInstance.getId())) {
                List<MetaClassInstance> incomingInstances = internalUmlService.queryListInstanceProperty(projectId,
                        instance, UMLDefinitionConstants.Vertex_incoming);
                return CollectionUtils.isNotEmpty(incomingInstances);
            }
            // 集合节点只能有一个传出
            if (UMLDefinitionConstants.PseudostateKind_join.equals(pseudoStateKindInstance.getId())) {
                List<MetaClassInstance> outgoingInstances = internalUmlService.queryListInstanceProperty(projectId,
                        instance, UMLDefinitionConstants.Vertex_outgoing);
                return CollectionUtils.isNotEmpty(outgoingInstances);
            }
            // 深、浅层历史连接器只能有一个传出
            if (StringUtils.equals(UMLDefinitionConstants.PseudostateKind_deepHistory, pseudoStateKindInstance.getId())
                    || StringUtils.equals(UMLDefinitionConstants.PseudostateKind_shallowHistory, pseudoStateKindInstance.getId())) {
                List<MetaClassInstance> outgoingInstances = internalUmlService.queryListInstanceProperty(projectId,
                        instance, UMLDefinitionConstants.Vertex_outgoing);
                return CollectionUtils.isNotEmpty(outgoingInstances);
            }
        }
        return false;
    }

    /**
     * 递归获得状态机
     */
    @Nullable
    public MetaClassInstance getStateMachine(String projectId, MetaClassInstance owner) {
        if(owner == null) {
            return null;
        }
        if(internalUmlService.checkInstanceType(projectId, owner, UMLDefinitionConstants.StateMachine)) {
            return owner;
        } else {
            MetaClassInstance ownerOwner = internalUmlService.queryInstanceProperty(projectId, owner, UMLDefinitionConstants.Element_owner);
            return getStateMachine(projectId, ownerOwner);
        }
    }

    /**
     * 根据State获得状态机
     */
    @NotNull
    private ActivityStereotypeWithDiagram getActivityByState(String projectId, String stateId) {
        ActivityStereotypeWithDiagram activityStereotypeWithDiagram = new ActivityStereotypeWithDiagram();
        activityStereotypeWithDiagram.setActivityDiagram(UMLDefinitionConstants.SysMLActivityDiagram);

        Project project = projectManager.getProject(projectId);
        Optional.ofNullable(project).ifPresent(p -> {
            MetaClassInstance state = this.internalUmlService.fetchInstance(stateId, projectId);
            Optional.ofNullable(state).ifPresent(s -> {
                MetaClassInstance stateMachine = this.getStateMachine(projectId, s);
                Optional.ofNullable(stateMachine).ifPresent(sm -> {
                    if (internalUmlService.checkInstanceType(projectId, sm, UMLDefinitionConstants.UAF_OperationalStateDescription)) {
                        handleOperationalStateDescription(p, activityStereotypeWithDiagram);
                    } else if (internalUmlService.checkInstanceType(projectId, sm, UMLDefinitionConstants.UAF_ResourceStateDescription)) {
                        handleResourceStateDescription(p, activityStereotypeWithDiagram);
                    } else if (internalUmlService.checkInstanceType(projectId, sm, UMLDefinitionConstants.UAF_ServiceStateDescription)) {
                        handleServiceStateDescription(p, activityStereotypeWithDiagram);
                    } else {
                        log.info(LogInfoEnum.STATE_DESCRIPTION_NOT_MATCH.getMsg());
                    }
                });
            });
        });
        return activityStereotypeWithDiagram;
    }

    /**
     * 处理ServiceStateDescription状态机
     *
     */
    private void handleServiceStateDescription(Project p, ActivityStereotypeWithDiagram activityStereotypeWithDiagram) {
        if(ProjectSpecificationEnum.UAF.value().equals(p.getProjectSpecification())) {
            activityStereotypeWithDiagram.setActivityDiagram(UAFDefinitionConstants.UAF_DIAGRAM_SVPR_SERVICESPROCESSFLOW);
        } else if(ProjectSpecificationEnum.DODAF.value().equals(p.getProjectSpecification()) ||
                ProjectSpecificationEnum.UPDM.value().equals(p.getProjectSpecification())) {
            activityStereotypeWithDiagram.setActivityDiagram(UAFDefinitionConstants.UAF_DIAGRAM_SVCV4_SERVICESFUNCTIONALITYFLOWDESCRIPTION);
        } else {
            log.info(LogInfoEnum.ACTIVITY_DIAGRAM_NOT_FOUND.getMsg());
        }
        activityStereotypeWithDiagram.getActivityAppliedStereotypes().add(UMLDefinitionConstants.UAF_ServiceFunction);
        activityStereotypeWithDiagram.getCallBehaviorActionAppliedStereotypes().add(UMLDefinitionConstants.UAF_ServiceFunctionAction);
    }

    /**
     * 处理ResourceStateDescription状态机
     *
     */
    private void handleResourceStateDescription(Project p, ActivityStereotypeWithDiagram activityStereotypeWithDiagram) {
        if(ProjectSpecificationEnum.UAF.value().equals(p.getProjectSpecification())) {
            activityStereotypeWithDiagram.setActivityDiagram(UAFDefinitionConstants.UAF_DIAGRAM_RSPR_RESOURCESPROCESSFLOW);
        } else if(ProjectSpecificationEnum.DODAF.value().equals(p.getProjectSpecification()) ||
                ProjectSpecificationEnum.UPDM.value().equals(p.getProjectSpecification())) {
            activityStereotypeWithDiagram.setActivityDiagram(UAFDefinitionConstants.UAF_DIAGRAM_SV4_SYSTEMSFUNCTIONALITYFLOWDESCRIPTION);
        } else {
            log.info(LogInfoEnum.ACTIVITY_DIAGRAM_NOT_FOUND.getMsg());
        }
        activityStereotypeWithDiagram.getActivityAppliedStereotypes().add(UMLDefinitionConstants.UAF_Function);
        activityStereotypeWithDiagram.getCallBehaviorActionAppliedStereotypes().add(UMLDefinitionConstants.UAF_FunctionAction);
    }

    /**
     * 处理OperationalStateDescription状态机
     *
     */
    private void handleOperationalStateDescription(Project p, ActivityStereotypeWithDiagram activityStereotypeWithDiagram) {
        if(ProjectSpecificationEnum.UAF.value().equals(p.getProjectSpecification())) {
            activityStereotypeWithDiagram.setActivityDiagram(UAFDefinitionConstants.UAF_DIAGRAM_OPPR_OPERATIONALPROCESSFLOW);
        } else if(ProjectSpecificationEnum.DODAF.value().equals(p.getProjectSpecification()) ||
                ProjectSpecificationEnum.UPDM.value().equals(p.getProjectSpecification())) {
            activityStereotypeWithDiagram.setActivityDiagram(UAFDefinitionConstants.UAF_DIAGRAM_OV5_OPERATIONALACTIVITYMODEL);
        } else {
            log.info(LogInfoEnum.ACTIVITY_DIAGRAM_NOT_FOUND.getMsg());
        }
        activityStereotypeWithDiagram.getActivityAppliedStereotypes().add(UMLDefinitionConstants.UAF_OperationalActivity);
        activityStereotypeWithDiagram.getCallBehaviorActionAppliedStereotypes().add(UMLDefinitionConstants.UAF_OperationalActivityAction);
    }

    @Data
    public static class ActivityStereotypeWithDiagram {
        private List<String> activityAppliedStereotypes = new ArrayList<>();
        private List<String> callBehaviorActionAppliedStereotypes = new ArrayList<>();
        private String activityDiagram;
    }
}
